package com.nulstudio.cloud.account.service;

import com.nulstudio.cloud.account.config.NulSecurityConfig;
import com.nulstudio.cloud.account.domain.vo.LoginVo;
import com.nulstudio.cloud.account.entity.NulInvite;
import com.nulstudio.cloud.common.entity.NulRole;
import com.nulstudio.cloud.common.entity.NulUserDetail;
import com.nulstudio.cloud.common.entity.NulAuthority;
import com.nulstudio.cloud.account.mapper.AccountMapper;
import com.nulstudio.cloud.common.exception.NulException;
import com.nulstudio.cloud.common.exception.NulExceptionConstants;
import com.nulstudio.cloud.common.util.NulJwtToken;
import jakarta.annotation.Resource;
import org.springframework.lang.NonNull;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Service for account-related operations.
 * @author nullsty
 * @since 2.0
 */
@Service
public final class AccountService {

    /**
     * Mapper for account module.
     */
    @Resource @NonNull
    private AccountMapper accountMapper;

    /**
     * Service for invite module.
     */
    @Resource @NonNull
    private InviteService inviteService;

    /**
     * Service for authority module.
     */
    @Resource @NonNull
    private AuthorityService authorityService;


    /**
     * Login to the system, and return token.
     * @param userName username of the user, must be non-null
     * @param password BCrypt encrypted password, must be non-null
     * @return a new token for the user
     * @throws NulException when the user does not exist, or the password is not correct
     */
    @NonNull
    public LoginVo login(@NonNull String userName, @NonNull String password) throws NulException {
        final NulUserDetail userDetail = getUserByUserName(userName);
        final NulRole role = authorityService.getRoleById(userDetail.getRole());
        NulRole.assertValidity(role);
        final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        if (!encoder.matches(password, userDetail.getPassword())) {
            throw new NulException(NulExceptionConstants.WRONG_PASSWORD);
        }
        final NulJwtToken token = NulJwtToken.generate(new NulJwtToken.NulJwtTokenProperties(
                userDetail.getUid(),
                role.getName(),
                role.getAuthorities().stream().map(NulAuthority::getAuthority).toList().toArray(new String[0])
        ));
        final List<String> authorities = authorityService.getRoleById(userDetail.getRole()).getAuthorities()
                .stream().map(NulAuthority::getAuthority).toList();
        return new LoginVo(token.token(), authorities);
    }

    /**
     * Create a new account.
     * @param userName username of the user, must be non-null
     * @param name nickname of the user, must be non-null
     * @param password plain-text password of the user, must be non-null
     * @param gender gender of the user, 1 represents male, 2 represents female, otherwise 0
     * @param inviteCode invite code of the registration, must be non-null
     * @throws NulException when user already exists, or failed to insert user
     */
    public void register(@NonNull String userName, @NonNull String name, @NonNull String password,
                         int gender, @NonNull String inviteCode) throws NulException {
        if (accountMapper.checkUserExistence(userName)) {
            throw new NulException(NulExceptionConstants.USER_ALREADY_EXIST);
        }
        final NulInvite invite = inviteService.getInvite(inviteCode);
        final String encryptedPassword = new BCryptPasswordEncoder().encode(password);
        final NulUserDetail userDetail = new NulUserDetail(gender, userName, name, encryptedPassword, invite.getRoleId());
        inviteService.checkInviteRole(invite);
        NulUserDetail.assertValidity(userDetail);
        NulInvite.assertValidity(invite);
        if (accountMapper.insertUser(userDetail) == 0) {
            throw new NulException(NulExceptionConstants.INTERNAL_SERVER_ERROR);
        }
        inviteService.registerInvite(invite);
    }

    /**
     * Fetch user information by ID.
     * @param uid ID of the user
     * @return information of the user
     * @throws NulException when the user does not exist
     */
    @NonNull
    public NulUserDetail getUserById(int uid) throws NulException {
        final NulUserDetail userDetail = accountMapper.getUserById(uid);
        if (userDetail == null) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
        return userDetail;
    }

    /**
     * Fetch user information by username.
     * @param name username of the user, must be non-null
     * @return information of the user
     * @throws NulException when the user does not exist
     */
    @NonNull
    public NulUserDetail getUserByUserName(@NonNull String name) throws NulException {
        final NulUserDetail userDetail = accountMapper.getUserByUserName(name);
        if (userDetail == null) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
        return userDetail;
    }

    /**
     * Get a list of all users.
     * @return a list of all users
     */
    @NonNull
    public List<NulUserDetail> getUserList() {
        return accountMapper.getUserList();
    }

    /**
     * Update name of the current user.
     * @param name new name of the user, must be non-null
     */
    public void updateCurrentUserName(@NonNull String name) {
        final int uid = NulSecurityConfig.getContextUserId();
        final int result = accountMapper.updateName(uid, name);
        if (result <= 0) {
            throw new NulException(NulExceptionConstants.USER_NOT_EXIST);
        }
    }
}
